/** @file res_shader_mgr.cpp
    @brief Shader manager */

#include "res/res_shader_mgr.h"

using namespace G3;

G3::SHADER_MANAGER G3::MainShaderManager;

//==============================================================================
// Constructor
//==============================================================================
G3::SHADER_MANAGER::SHADER_MANAGER () {
	Log = &G3::MainLog;
}

//==============================================================================
// Destructor
//==============================================================================
G3::SHADER_MANAGER::~SHADER_MANAGER () {
	std::map<std::string, H_VSHADER>::iterator vsit;
	std::map<std::string, H_PSHADER>::iterator psit;
	boost::shared_ptr<SHADER_BASE> pShader;

	// Loop through the VSNameIndex
	vsit = VSNameIndex.begin ();
	while (vsit != VSNameIndex.end ()) {
        if (!vsit->second.IsNull ()) {
		  pShader = VertexShaders.GetData (vsit->second);
		  // And clear all shaders
	   	  if (pShader)
	   	  	  pShader->Clear ();
        }

		vsit++;
	}

	// Loop through the PSNameIndex
	psit = PSNameIndex.begin ();
	while (psit != PSNameIndex.end ()) {
        if (!psit->second.IsNull ()) {
		  pShader = PixelShaders.GetData (psit->second);
		  // And clear all shaders
	   	  if (pShader)
	   	  	  pShader->Clear ();
        }

		psit++;
	}
}

//==============================================================================
// Gets the filename of the shader
//==============================================================================
std::string& G3::SHADER_MANAGER::GetShaderName (uint handle, SHADER_PROFILE profile) {
    boost::shared_ptr<SHADER_BASE> pShader;
    static string EmptyString;

    switch (profile) {
        case SP_VERTEX:
            pShader = VertexShaders.GetData ((H_VSHADER) handle);
            break;

        case SP_FRAGMENT:
            pShader = PixelShaders.GetData ((H_PSHADER) handle);
            break;

        default: break;
    }

	if (pShader)
		return pShader->filename;

	return EmptyString;
}

//==============================================================================
// Gets the shader handle by filename
//
// And loads the shader if needed
//==============================================================================
uint G3::SHADER_MANAGER::GetShader (const string &filename, SHADER_PROFILE profile) {
	Assert (!filename.empty (), "SHADER_MANAGER::GetShader: Filename argument is an empty string");

	std::pair <std::map<std::string, H_VSHADER>::iterator, bool> nipairv;
	std::pair <std::map<std::string, H_PSHADER>::iterator, bool> nipairp;
	boost::shared_ptr<SHADER_BASE> pShader;
	std::string fPath;

	switch (profile) {
	    case SP_VERTEX:
            // Try to insert this new vertex shader
            nipairv = VSNameIndex.insert (std::pair<std::string, H_VSHADER> (filename, H_VSHADER ()));

            // Success ?
            if (nipairv.second) {
                // Acquire a new shader
                pShader = VertexShaders.Acquire (nipairv.first->second);

                if (pShader) {
                    fPath = MainFilesys.GetPath (filename.c_str ());

                    if (fPath.size () > 1 && !pShader->Load (fPath)) {
                        if (Log)
                            Log->Report ("Failed to load the vertex shader \"%s\"..\n", filename.c_str ());

                        DeleteVS (nipairv.first->second);
                        nipairv.first->second = H_VSHADER ();
                    }
                } else {
                    if (Log)
                        Log->Report ("SHADER_MANAGER::GetShader: Could not acquire memory for a vertex shader\n");
                }
            }
            return nipairv.first->second;

	    case SP_FRAGMENT:
            // Try to insert this new pixel shader
            nipairp = PSNameIndex.insert (std::pair<std::string, H_PSHADER> (filename, H_PSHADER ()));

            // Success ?
            if (nipairp.second) {
                // Acquire a new shader
                pShader = PixelShaders.Acquire (nipairp.first->second);

                if (pShader) {
                    fPath = MainFilesys.GetPath (filename.c_str ());

                    if (fPath.size () > 1 && !pShader->Load (fPath)) {
                        if (Log)
                            Log->Report ("Failed to load the fragment shader \"%s\"..\n", filename.c_str ());

                        DeletePS (nipairp.first->second);
                        nipairp.first->second = H_PSHADER ();
                    }
                } else {
                    if (Log)
                        Log->Report ("SHADER_MANAGER::GetShader: Could not acquire memory for a fragment shader\n");
                }
            }
            return nipairp.first->second;

        default: break;
    }

	return 0;
}

//==============================================================================
// Gets a temporary pointer to the shader class
//==============================================================================
boost::shared_ptr<VERTEX_SHADER> G3::SHADER_MANAGER::GetVSPointer (H_VSHADER Handle) {
    Assert (!Handle.IsNull (), "SHADER_MANAGER::GetVSPointer: Handle argument is Null");
	// Gets a pointer to the shader class
	return VertexShaders.GetData (Handle);
}

boost::shared_ptr<FRAGMENT_SHADER> G3::SHADER_MANAGER::GetPSPointer (H_PSHADER Handle) {
    Assert (!Handle.IsNull (), "SHADER_MANAGER::GetPSPointer: Handle argument is Null");
	// Gets a pointer to the shader class
	return PixelShaders.GetData (Handle);
}

//==============================================================================
// Gets the fileformat of a shader
//==============================================================================
SHADER_FORMAT G3::SHADER_MANAGER::GetVSFormat (H_VSHADER Handle) {
    Assert (!Handle.IsNull (), "SHADER_MANAGER::GetVSFormat: Handle argument is Null");

	boost::shared_ptr<VERTEX_SHADER> pShader = GetVSPointer (Handle);

	if (!pShader)
		return SF_UNKNOWN;

	return pShader->shaderFormat;
}

SHADER_FORMAT G3::SHADER_MANAGER::GetPSFormat (H_PSHADER Handle) {
    Assert (!Handle.IsNull (), "SHADER_MANAGER::GetPSFormat: Handle argument is Null");

	boost::shared_ptr<FRAGMENT_SHADER> pShader = GetPSPointer (Handle);

	if (!pShader)
		return SF_UNKNOWN;

	return pShader->shaderFormat;
}

//==============================================================================
// Gets the identifier of the shader
//==============================================================================
uint G3::SHADER_MANAGER::GetVSID (H_VSHADER Handle) {
    Assert (!Handle.IsNull (), "SHADER_MANAGER::GetVSID: Handle argument is Null");

	boost::shared_ptr<VERTEX_SHADER> pShader = GetVSPointer (Handle);

	if (!pShader)
		return 0;

	return pShader->programID;
}

uint G3::SHADER_MANAGER::GetPSID (H_PSHADER Handle) {
    Assert (!Handle.IsNull (), "SHADER_MANAGER::GetPSID: Handle argument is Null");

	boost::shared_ptr<FRAGMENT_SHADER> pShader = GetPSPointer (Handle);

	if (!pShader)
		return 0;

	return pShader->programID;
}

//==============================================================================
// Applies the shader
//==============================================================================
void G3::SHADER_MANAGER::ApplyVS (H_VSHADER Handle) {
    Assert (!Handle.IsNull (), "SHADER_MANAGER::ApplyVS: Handle argument is Null");

	boost::shared_ptr<VERTEX_SHADER> pShader = GetVSPointer (Handle);

	if (pShader)
		pShader->Apply ();
}

void G3::SHADER_MANAGER::ApplyPS (H_PSHADER Handle) {
    Assert (!Handle.IsNull (), "SHADER_MANAGER::ApplyPS: Handle argument is Null");

	boost::shared_ptr<FRAGMENT_SHADER> pShader = GetPSPointer (Handle);

	if (pShader)
		pShader->Apply ();
}

//==============================================================================
// UnApplies the shader
//==============================================================================
void G3::SHADER_MANAGER::UnApplyVS (H_VSHADER Handle) {
    Assert (!Handle.IsNull (), "SHADER_MANAGER::UnApplyVS: Handle argument is Null");

	boost::shared_ptr<VERTEX_SHADER> pShader = GetVSPointer (Handle);

	if (pShader)
		pShader->UnApply ();
}

void G3::SHADER_MANAGER::UnApplyPS (H_PSHADER Handle) {
    Assert (!Handle.IsNull (), "SHADER_MANAGER::UnApplyPS: Handle argument is Null");

	boost::shared_ptr<FRAGMENT_SHADER> pShader = GetPSPointer (Handle);

	if (pShader)
		pShader->UnApply ();
}

//==============================================================================
// Deletes the shader
//==============================================================================
void G3::SHADER_MANAGER::DeleteVS (H_VSHADER Handle) {
	boost::shared_ptr<VERTEX_SHADER> pShader = VertexShaders.GetData (Handle);
	std::map<std::string, H_VSHADER>::iterator it = VSNameIndex.end ();
	// Is there such a shader?
	if (pShader) {
		it = VSNameIndex.find (pShader->filename);

		if (it != VSNameIndex.end ())
			VSNameIndex.erase (it);

		// Also deletes it from the video card
		pShader->Clear ();
		// Frees it in the handle manager
		VertexShaders.Release (Handle);
	}
}

void G3::SHADER_MANAGER::DeletePS (H_PSHADER Handle) {
	boost::shared_ptr<FRAGMENT_SHADER> pShader = PixelShaders.GetData (Handle);
	std::map<std::string, H_PSHADER>::iterator it = PSNameIndex.end ();
	// Is there such a shader?
	if (pShader) {
		it = PSNameIndex.find (pShader->filename);

		if (it != PSNameIndex.end ())
			PSNameIndex.erase (it);

		// Also deletes it from the video card
		pShader->Clear ();
		// Frees it in the handle manager
		PixelShaders.Release (Handle);
	}
}

//==============================================================================
// Dumps the whole shader manager into the log
//==============================================================================
void G3::SHADER_MANAGER::Dump () {
	std::map<std::string, H_VSHADER>::iterator vsit;
	std::map<std::string, H_PSHADER>::iterator psit;

	if (Log) {
		Log->Report ("Dumping Vertex Shader Name Index:\n");

		if (!VSNameIndex.empty ()) {
			vsit = VSNameIndex.begin ();
			while (vsit != VSNameIndex.end ()) {
				Log->Report (" \"%s\" -> 0x%X\n", vsit->first.c_str (), (uint) vsit->second);

				vsit++;
			}
		} else {
			Log->Report ("<Empty>\n");
		}

		VertexShaders.Dump ();

		Log->Report ("Dumping Pixel Shader Name Index:\n");

		if (!PSNameIndex.empty ()) {
			psit = PSNameIndex.begin ();
			while (psit != PSNameIndex.end ()) {
				Log->Report (" \"%s\" -> 0x%X\n", psit->first.c_str (), (uint) psit->second);

				psit++;
			}
		} else {
			Log->Report ("<Empty>\n");
		}

		PixelShaders.Dump ();
	}
}
